﻿using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
using Microsoft.AspNet.Mvc.Facebook;
using Microsoft.AspNet.Mvc.Facebook.Client;
using MakeWeekConcerts.Models;
using System.Net.Http;
using HtmlAgilityPack;
using System;
using System.Diagnostics;
using System.Runtime.Caching;
using Google.Apis.YouTube.v3;
using Google.Apis.Services;
using Google.Apis.YouTube.v3.Data;

namespace MakeWeekConcerts.Controllers
{
    public class HomeController : Controller
    {
        private static MemoryCache FriendsCache = new MemoryCache("Friends");

        [FacebookAuthorize("email", "user_photos", "user_likes", "user_location", "friends_likes", "friends_location")]
        public async Task<ActionResult> Index(FacebookContext context)
        {
            if (ModelState.IsValid)
            {
                var user = await context.Client.GetCurrentUserAsync<FacebookUser>();
                var similar = await user.FindSimilarArtists();

                var shows = DataLoader.GetConcerts();
                var interestingShows = new List<MyInterestingShow>();
                foreach (var show in shows.ConcertList)
                {
                    var curMatches = similar.Keys.Intersect(show.Artists);
                    var curSimilar = similar.SelectMany(x => x.Value).Intersect(show.Artists);
                    if (curMatches.Any() || curSimilar.Any())
                    {
                        var interesting = new MyInterestingShow() { Show = show };
                        interesting.BandsILike.AddRange(curMatches);
                        foreach (var cur in similar)
                        {
                            var x = cur.Value.Intersect(curSimilar);
                            if (x.Any())
                            {
                                interesting.SimilarBands[cur.Key] = x.ToList();
                            }
                        }
                        interestingShows.Add(interesting);
                    }
                }
                return View(new MyInterestingShows() { Self = user, InterestingShows = interestingShows });
            }

            return View("Error");
        }

        public async Task<ActionResult> ArtistDetails(string artistName, FacebookContext context)
        {
            if (ModelState.IsValid)
            {
                var artistResp = await ArtistDetailResponse.GetArtistDetail(artistName);

                return View(artistResp.Artist);
            }

            return View("Error");
        }

        public async Task<ActionResult> Friends(FacebookContext context)
        {
            if (ModelState.IsValid)
            {
                var user = await context.Client.GetCurrentUserAsync<FacebookUser>();
                return View(user);
            }

            return View("Error");
        }

        public async Task<ActionResult> FriendDetails(string id, FacebookContext context)
        {
            if (ModelState.IsValid)
            {
                var self = await context.Client.GetCurrentUserAsync<FacebookUser>();
                var friend = await GetFriend(id, context);
                var overlaps = await friend.FindArtistOverlap(self);
                return View(new MyFriendDetails() { Friend = friend, SharedArtists = overlaps.Item1, SharedSimilar = overlaps.Item2 });
            }

            return View("Error");
        }

        public async Task<ActionResult> ConcertDetails(string id, FacebookContext context)
        {
            if (ModelState.IsValid)
            {
                var evt = DataLoader.GetConcerts().ConcertList.Single(x => x.Id == id);
                var artists = new HashSet<string>(evt.Artists);
                var similar = await GetSimilarArtists(evt.Artists);
                var self = await context.Client.GetCurrentUserAsync<FacebookUser>();
                var result = new MyShowPlanner() { Self = self, Show = evt };
                foreach (var artist in evt.Artists)
                {
                    var vids = await YoutubeVideo.GetVideosFor(artist);
                    result.Videos.AddRange(vids.Take(2));
                }

                foreach (var friend in self.Friends.Data)
                {
                    var friendDtls = await GetFriend(friend.Id, context);
                    if (friendDtls.Music == null || friendDtls.Music.Data == null || !friendDtls.Music.Data.Any()) continue;

                    var friendArtists = new HashSet<string>(friendDtls.Music.Data.Select(x => x.Name), StringComparer.OrdinalIgnoreCase);
                    var interestedInShow = artists.Intersect(friendArtists);
                    var mightBeInterested = similar.SelectMany(x => x.Value).Intersect(friendArtists);
                    if (interestedInShow.Any() || mightBeInterested.Any())
                    {
                        var friendData = new MyInterestedFriend() { Friend = friendDtls };
                        friendData.BandsTheyLike.AddRange(interestedInShow);
                        foreach (var interest in similar)
                        {
                            var current = interest.Value.Intersect(mightBeInterested);
                            if (current.Any())
                            {
                                friendData.SimilarBands[interest.Key] = current.ToList();
                            }
                        }
                        result.Friends.Add(friendData);
                    }
                }

                return View(result);
            }

            return View("Error");
        }

        private async Task<Dictionary<string, List<string>>> GetSimilarArtists(List<string> artists, double minimumThreshold = 0.3)
        {
            var result = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
            foreach (var artist in artists)
            {
                var cur = await DataLoader.FindSimilarArtistsAsync(artist);
                if (cur != null && cur.SimilarArtists != null && cur.SimilarArtists.Artists.Any(x => x.Match > minimumThreshold))
                {
                    result[artist] = cur.SimilarArtists.Artists.Where(x => x.Match > minimumThreshold).Select(x => x.Name).ToList();
                }
            }
            return result;
        }

        private async Task<FacebookFriend> GetFriend(string id, FacebookContext context)
        {
            FacebookFriend friend = null;
            if (FriendsCache.Contains(id))
            {
                friend = (FacebookFriend)FriendsCache.Get(id);
            }
            else
            {
                friend = await context.Client.GetFacebookObjectAsync<FacebookFriend>(FriendPath(id));
                FriendsCache.Add(id, friend, new CacheItemPolicy() { SlidingExpiration = TimeSpan.FromHours(1) });
            }
            return friend;
        }

        private static string FriendPath(string id)
        {
            return string.Format("/{0}", id);
        }

        // This action will handle the redirects from FacebookAuthorizeFilter when
        // the app doesn't have all the required permissions specified in the FacebookAuthorizeAttribute.
        // The path to this action is defined under appSettings (in Web.config) with the key 'Facebook:AuthorizationRedirectPath'.
        public ActionResult Permissions(FacebookRedirectContext context)
        {
            if (ModelState.IsValid)
            {
                return View(context);
            }

            return View("Error");
        }

        public ActionResult Upcoming()
        {
            return View(DataLoader.GetConcerts());
        }
    }
}
